package com.paul.sys.tools.dbutil.dialect;

import com.paul.sys.tools.CommPageBean;
import org.apache.ibatis.executor.parameter.ParameterHandler;
import org.apache.ibatis.executor.statement.StatementHandler;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.plugin.*;
import org.apache.ibatis.reflection.MetaObject;
import org.apache.ibatis.reflection.factory.DefaultObjectFactory;
import org.apache.ibatis.reflection.factory.ObjectFactory;
import org.apache.ibatis.reflection.wrapper.DefaultObjectWrapperFactory;
import org.apache.ibatis.reflection.wrapper.ObjectWrapperFactory;
import org.apache.ibatis.scripting.defaults.DefaultParameterHandler;
import org.apache.ibatis.session.Configuration;
import org.apache.ibatis.session.RowBounds;
import org.apache.log4j.Logger;

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.Properties;

@Intercepts( { @Signature(type = StatementHandler.class, method = "prepare", args = { Connection.class }) })
public class PaginationInterceptor implements Interceptor {
    protected static Logger log = Logger.getLogger(PaginationInterceptor.class);
    private static String defaultPageSqlId = ".*Page$";  // 需要拦截的ID(正则匹配)
    private static String pageSqlId = ""; // 需要拦截的ID(正则匹配)

    private static final ObjectFactory DEFAULT_OBJECT_FACTORY = new DefaultObjectFactory();
    private static final ObjectWrapperFactory DEFAULT_OBJECT_WRAPPER_FACTORY = new DefaultObjectWrapperFactory();
    
    private static final OracleDialect orcleDialect = new OracleDialect();
    private static final MysqlDialect mysqlDialect = new MysqlDialect();
    private static final Mssql58Dialect mssql58Dialect = new Mssql58Dialect();
    private static final Mssql12Dialect mssql12Dialect = new Mssql12Dialect();
    private static final DB2Dialect db2Dialect = new DB2Dialect();

    /*
     * (non-Javadoc)
     * @see
     * org.apache.ibatis.plugin.Interceptor#intercept(org.apache.ibatis.plugin
     * .Invocation)
     */
    @Override
    public Object intercept(Invocation invocation) throws Throwable
    {
        StatementHandler statementHandler = (StatementHandler) invocation.getTarget();

        MetaObject metaStatementHandler = MetaObject.forObject(statementHandler, DEFAULT_OBJECT_FACTORY,
                DEFAULT_OBJECT_WRAPPER_FACTORY);

        // 分离代理对象链(由于目标类可能被多个拦截器拦截，从而形成多次代理，通过下面的两次循环可以分离出最原始的的目标类)
        while (metaStatementHandler.hasGetter("h")) {
            Object object = metaStatementHandler.getValue("h");
            metaStatementHandler = MetaObject.forObject(object, DEFAULT_OBJECT_FACTORY, DEFAULT_OBJECT_WRAPPER_FACTORY);
        }
     // 分离最后一个代理对象的目标类
        while (metaStatementHandler.hasGetter("target")) {
            Object object = metaStatementHandler.getValue("target");
            metaStatementHandler = MetaObject.forObject(object, DEFAULT_OBJECT_FACTORY, DEFAULT_OBJECT_WRAPPER_FACTORY);
        }

        Configuration configuration = (Configuration) metaStatementHandler.getValue("delegate.configuration");

        pageSqlId = configuration.getVariables().getProperty("pageSqlId");
        if (null == pageSqlId || "".equals(pageSqlId)) {
        	log.warn("Property pageSqlId is not setted,use default '.*Page$' ");
            pageSqlId = defaultPageSqlId;
        }
        MappedStatement mappedStatement = (MappedStatement) metaStatementHandler.getValue("delegate.mappedStatement");
        // 只重写需要分页的sql语句。通过MappedStatement的ID匹配，默认重写以Page结尾的MappedStatement的sql
        if (mappedStatement.getId().matches(pageSqlId)) {
            BoundSql boundSql = (BoundSql) metaStatementHandler.getValue("delegate.boundSql");
            Object parameterObject = boundSql.getParameterObject();
            if (parameterObject == null) {
                throw new NullPointerException("parameterObject is null!");
            } else {
                Dialect.Type databaseType = null;
                try
                {
                    databaseType = Dialect.Type.valueOf(configuration.getVariables().getProperty("dialect").toUpperCase());
                }
                catch (Exception e)
                {
                	
                }
                if (databaseType == null)
                {
                    throw new RuntimeException(
                            "the value of the dialect property in configuration.xml is not defined : "
                                    + configuration.getVariables().getProperty("dialect"));
                }
                
                Dialect dialect = null;
                switch (databaseType)
                {
	                case ORACLE:
	                    dialect = orcleDialect;
	                    break;
	                case DB2:
	                    dialect = db2Dialect;
	                    break;
	                case MYSQL:
	                    dialect = mysqlDialect;
	                    break;
	                case MSSQL58:
	                    dialect = mssql58Dialect;
	                    break;
	                case MSSQL12:
	                    dialect = mssql12Dialect;
	                    break;
	                default:
	                    break;
                }
                

                CommPageBean page = (CommPageBean) metaStatementHandler.getValue("delegate.boundSql.parameterObject.page");
                String sql = boundSql.getSql();
                // 重写sql
                String pageSql = dialect.getLimitString(sql, page.getPageSize()*(page.getCurPage()-1), page.getPageSize());
            
                log.debug(pageSql);
            
                metaStatementHandler.setValue("delegate.boundSql.sql", pageSql);
                // 采用物理分页后，就不需要mybatis的内存分页了，所以重置下面的两个参数
                metaStatementHandler.setValue("delegate.rowBounds.offset", RowBounds.NO_ROW_OFFSET);
                metaStatementHandler.setValue("delegate.rowBounds.limit", RowBounds.NO_ROW_LIMIT);
                Connection connection = (Connection) invocation.getArgs()[0];
                // 重设分页参数里的总页数等
                setPageParameter(sql, connection, mappedStatement, boundSql, page);
            }
        }
        // 将执行权交给下一个拦截器
        return invocation.proceed();
    }
    
    /**
     * 去除排序带来的性能开销
     * @param sql
     * @return
     */
    private static String cutOrderBySql(String sql) {
    	String SQL = sql.toUpperCase();
    	int orderIdx = SQL.indexOf("ORDER");
    	if (orderIdx == -1) {
    		return sql;
    	}
    	
    	SQL = SQL.substring(orderIdx+5).trim();
    	if (!SQL.startsWith("BY")) {
    		return sql;
    	}
    	
    	int endOrderIdx = SQL.indexOf(")");
    	if (endOrderIdx == -1) {
    		return sql.substring(0, orderIdx).trim();
    	} 
    	
    	return sql.substring(0, orderIdx).trim() + sql.substring(orderIdx+5+endOrderIdx+1);
    }
    
    /**
     * 从数据库里查询总的记录数并计算总页数，回写进分页参数<code>PageParameter</code>,这样调用者就可用通过 分页参数
     * <code>PageParameter</code>获得相关信息。
     * 
     * @param sql
     * @param connection
     * @param mappedStatement
     * @param boundSql
     * @param page
     */
    private void setPageParameter(String sql, Connection connection, MappedStatement mappedStatement,
            BoundSql boundSql, CommPageBean commPageBean) {
    	
    	sql = cutOrderBySql(sql);
    	
    	 // 记录总记录数
        String countSql = "select count(0) as total from (" + sql + ") as t ";
        PreparedStatement countStmt = null;
        ResultSet rs = null;
        try {
            countStmt = connection.prepareStatement(countSql);
            BoundSql countBS = new BoundSql(mappedStatement.getConfiguration(), countSql,
                    boundSql.getParameterMappings(), boundSql.getParameterObject());
            setParameters(countStmt, mappedStatement, countBS, boundSql.getParameterObject());
            rs = countStmt.executeQuery();
            int totalCount = 0;
            if (rs.next()) {
                totalCount = rs.getInt(1);
            }
            commPageBean.setTotalNum(totalCount);
            int totalPage = totalCount / commPageBean.getPageSize() + ((totalCount % commPageBean.getPageSize() == 0) ? 0 : 1);
            commPageBean.setTotalPage(totalPage);

        } catch (SQLException e) {
            log.error("Ignore this exception", e);
        } finally {
            try {
                rs.close();
            } catch (SQLException e) {
                log.error("Ignore this exception", e);
            }
            try {
                countStmt.close();
            } catch (SQLException e) {
                log.error("Ignore this exception", e);
            }
        }

    }
    
    /**
     * 对SQL参数(?)设值
     * 
     * @param ps
     * @param mappedStatement
     * @param boundSql
     * @param parameterObject
     * @throws SQLException
     */
    private void setParameters(PreparedStatement ps, MappedStatement mappedStatement, BoundSql boundSql,
            Object parameterObject) throws SQLException {
        ParameterHandler parameterHandler = new DefaultParameterHandler(mappedStatement, parameterObject, boundSql);
        parameterHandler.setParameters(ps);
    }

    /*
     * (non-Javadoc)
     * @see org.apache.ibatis.plugin.Interceptor#plugin(java.lang.Object)
     */
    @Override
    public Object plugin(Object target)
    {
    	 // 当目标类是StatementHandler类型时，才包装目标类，否者直接返回目标本身,减少目标被代理的次数
        if (target instanceof StatementHandler) {
            return Plugin.wrap(target, this);
        } else {
            return target;
        }
    }

    /*
     * (non-Javadoc)
     * @see
     * org.apache.ibatis.plugin.Interceptor#setProperties(java.util.Properties)
     */
    @Override
    public void setProperties(Properties arg0)
    {
        // TODO Auto-generated method stub

    }

}
